/***************************************************************************/
/*                                                                         */
/*  ftcmru.c                                                               */
/*                                                                         */
/*    FreeType MRU support (body).                                         */
/*                                                                         */
/*  Copyright 2003, 2004, 2006, 2009 by                                    */
/*  David Turner, Robert Wilhelm, and Werner Lemberg.                      */
/*                                                                         */
/*  This file is part of the FreeType project, and may only be used,       */
/*  modified, and distributed under the terms of the FreeType project      */
/*  license, LICENSE.TXT.  By continuing to use, modify, or distribute     */
/*  this file you indicate that you have read the license and              */
/*  understand and accept it fully.                                        */
/*                                                                         */
/***************************************************************************/


#include <ft2build.h>
#include FT_CACHE_H
#include "ftcmru.h"
#include FT_INTERNAL_OBJECTS_H
#include FT_INTERNAL_DEBUG_H

#include "ftcerror.h"


FT_LOCAL_DEF(void)
FTC_MruNode_Prepend(FTC_MruNode * plist,
                    FTC_MruNode node)
{
    FTC_MruNode first = *plist;


    if (first)
    {
        FTC_MruNode last = first->prev;


        #ifdef FT_DEBUG_ERROR
        {
            FTC_MruNode cnode = first;


            do
            {
                if (cnode == node)
                {
                    fprintf(stderr, "FTC_MruNode_Prepend: invalid action\n");
                    exit(2);
                }
                cnode = cnode->next;

            }
            while (cnode != first);
        }
        #endif

        first->prev = node;
        last->next = node;
        node->next = first;
        node->prev = last;
    }
    else
    {
        node->next = node;
        node->prev = node;
    }
    *plist = node;
}


FT_LOCAL_DEF(void)
FTC_MruNode_Up(FTC_MruNode * plist,
               FTC_MruNode node)
{
    FTC_MruNode first = *plist;


    FT_ASSERT(first != NULL);

    if (first != node)
    {
        FTC_MruNode prev, next, last;


        #ifdef FT_DEBUG_ERROR
        {
            FTC_MruNode cnode = first;
            do
            {
                if (cnode == node)
                    goto Ok;
                cnode = cnode->next;

            }
            while (cnode != first);

            fprintf(stderr, "FTC_MruNode_Up: invalid action\n");
            exit(2);
Ok:
        }
        #endif
        prev = node->prev;
        next = node->next;

        prev->next = next;
        next->prev = prev;

        last = first->prev;

        last->next = node;
        first->prev = node;

        node->next = first;
        node->prev = last;

        *plist = node;
    }
}


FT_LOCAL_DEF(void)
FTC_MruNode_Remove(FTC_MruNode * plist,
                   FTC_MruNode node)
{
    FTC_MruNode first = *plist;
    FTC_MruNode prev, next;


    FT_ASSERT(first != NULL);

    #ifdef FT_DEBUG_ERROR
    {
        FTC_MruNode cnode = first;


        do
        {
            if (cnode == node)
                goto Ok;
            cnode = cnode->next;

        }
        while (cnode != first);

        fprintf(stderr, "FTC_MruNode_Remove: invalid action\n");
        exit(2);
Ok:
    }
    #endif

    prev = node->prev;
    next = node->next;

    prev->next = next;
    next->prev = prev;

    if (node == next)
    {
        FT_ASSERT(first == node);
        FT_ASSERT(prev == node);

        *plist = NULL;
    }
    else if (node == first)
        *plist = next;
}


FT_LOCAL_DEF(void)
FTC_MruList_Init(FTC_MruList list,
                 FTC_MruListClass clazz,
                 FT_UInt max_nodes,
                 FT_Pointer data,
                 FT_Memory memory)
{
    list->num_nodes = 0;
    list->max_nodes = max_nodes;
    list->nodes = NULL;
    list->clazz = *clazz;
    list->data = data;
    list->memory = memory;
}


FT_LOCAL_DEF(void)
FTC_MruList_Reset(FTC_MruList list)
{
    while (list->nodes)
        FTC_MruList_Remove(list, list->nodes);

    FT_ASSERT(list->num_nodes == 0);
}


FT_LOCAL_DEF(void)
FTC_MruList_Done(FTC_MruList list)
{
    FTC_MruList_Reset(list);
}


#ifndef FTC_INLINE
FT_LOCAL_DEF(FTC_MruNode)
FTC_MruList_Find(FTC_MruList list,
                 FT_Pointer key)
{
    FTC_MruNode_CompareFunc compare = list->clazz.node_compare;
    FTC_MruNode first, node;


    first = list->nodes;
    node = NULL;

    if (first)
    {
        node = first;
        do
        {
            if (compare(node, key))
            {
                if (node != first)
                    FTC_MruNode_Up(&list->nodes, node);

                return node;
            }

            node = node->next;

        }
        while (node != first);
    }

    return NULL;
}
#endif

FT_LOCAL_DEF(FT_Error)
FTC_MruList_New(FTC_MruList list,
                FT_Pointer key,
                FTC_MruNode * anode)
{
    FT_Error error;
    FTC_MruNode node;
    FT_Memory memory = list->memory;


    if (list->num_nodes >= list->max_nodes && list->max_nodes > 0)
    {
        node = list->nodes->prev;

        FT_ASSERT(node);

        if (list->clazz.node_reset)
        {
            FTC_MruNode_Up(&list->nodes, node);

            error = list->clazz.node_reset(node, key, list->data);
            if (!error)
                goto Exit;
        }

        FTC_MruNode_Remove(&list->nodes, node);
        list->num_nodes--;

        if (list->clazz.node_done)
            list->clazz.node_done(node, list->data);
    }
    else if (FT_ALLOC(node, list->clazz.node_size))
        goto Exit;

    error = list->clazz.node_init(node, key, list->data);
    if (error)
        goto Fail;

    FTC_MruNode_Prepend(&list->nodes, node);
    list->num_nodes++;

Exit:
    *anode = node;
    return error;

Fail:
    if (list->clazz.node_done)
        list->clazz.node_done(node, list->data);

    FT_FREE(node);
    goto Exit;
}


#ifndef FTC_INLINE
FT_LOCAL_DEF(FT_Error)
FTC_MruList_Lookup(FTC_MruList list,
                   FT_Pointer key,
                   FTC_MruNode * anode)
{
    FTC_MruNode node;


    node = FTC_MruList_Find(list, key);
    if (node == NULL)
        return FTC_MruList_New(list, key, anode);

    *anode = node;
    return 0;
}
#endif /* FTC_INLINE */

FT_LOCAL_DEF(void)
FTC_MruList_Remove(FTC_MruList list,
                   FTC_MruNode node)
{
    FTC_MruNode_Remove(&list->nodes, node);
    list->num_nodes--;

    {
        FT_Memory memory = list->memory;


        if (list->clazz.node_done)
            list->clazz.node_done(node, list->data);

        FT_FREE(node);
    }
}


FT_LOCAL_DEF(void)
FTC_MruList_RemoveSelection(FTC_MruList list,
                            FTC_MruNode_CompareFunc selection,
                            FT_Pointer key)
{
    FTC_MruNode first, node, next;


    first = list->nodes;
    while (first && (selection == NULL || selection(first, key)))
    {
        FTC_MruList_Remove(list, first);
        first = list->nodes;
    }

    if (first)
    {
        node = first->next;
        while (node != first)
        {
            next = node->next;

            if (selection(node, key))
                FTC_MruList_Remove(list, node);

            node = next;
        }
    }
}


/* END */